package cn.redragon.soa.aspect;


import cn.redragon.soa.common.annotation.SystemLog;
import cn.redragon.soa.common.constant.AuthConstant;
import cn.redragon.soa.common.util.JsonUtil;
import cn.redragon.soa.model.dto.SystemLogDTO;
import cn.redragon.soa.service.SystemLogService;
import io.swagger.v3.oas.annotations.Operation;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.io.InputStreamSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
@ConditionalOnClass(JpaRepository.class)
public class SystemLogAspect {

    private final List<Class<?>> STRINGED_CLASSES = List.of(Number.class, Character.class, Boolean.class, String.class);
    private final List<String> IGNORED = List.of(AuthConstant.AUTH_HEADER, AuthConstant.TOKEN_HEADER);
    private final SystemLogService systemLogService;

    @Around("@annotation(cn.redragon.soa.common.annotation.SystemLog)")
    public Object handle(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        LocalDateTime startTime = LocalDateTime.now();
        try {
            result = pjp.proceed();
            return result;
        } catch (Exception e) {
            this.writeLog(startTime, e.getMessage(), pjp);
            throw e;
        } finally {
            if (Objects.nonNull(result)) {
                try {
                    writeLog(startTime, result, pjp);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    private void writeLog(LocalDateTime startTime, Object result, ProceedingJoinPoint pjp) {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method = ms.getMethod();

        SystemLog annotation = method.getAnnotation(SystemLog.class);
        if (annotation == null) {
            return;
        }

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Map<String, String> requestHeader = new HashMap<>();
        Enumeration<String> header = request.getHeaderNames();

        for (Iterator<String> it = header.asIterator(); it.hasNext(); ) {
            final String name = it.next();
            if (IGNORED.stream().anyMatch(e -> e.equalsIgnoreCase(name))) {
                continue;
            }
            requestHeader.put(name, request.getHeader(name));
        }

        Map<String, String> param = new HashMap<>();
        Object[] paramValues = pjp.getArgs();
        String[] paramNames = ((CodeSignature) pjp.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            String paramName = paramNames[i];
            Object paramValue = paramValues[i];
            if (IGNORED.contains(paramName) || paramValue instanceof InputStreamSource) {
                continue;
            }
            param.put(paramNames[i], this.toStringValue(paramValue));
        }

        String description;
        Operation api = method.getAnnotation(Operation.class);
        if (api != null) {
            description = annotation.module() + " ->> " + api.summary();
        } else {
            description = annotation.module() + " ->> " + method.getName();
        }

        SystemLogDTO dto = SystemLogDTO.builder()
            .brand(param.get("brand"))
            .module(annotation.module())
            .url(request.getRequestURI())
            .actionType(request.getMethod())
            .description(description.length() > 255 ? description.substring(0, 255) : description)
            .requestHeader(JsonUtil.objectToJsonStr(requestHeader))
            .requestPayload(JsonUtil.objectToJsonStr(param))
            .response(JsonUtil.objectToJsonStr(result))
            .createTime(startTime)
            .build();
        systemLogService.create(dto);
    }

    private String toStringValue(Object paramValue) {
        if (paramValue == null) {
            return null;
        }
        Class<?> clazz = paramValue.getClass();
        boolean stringed = clazz.isPrimitive() || clazz.isEnum() || STRINGED_CLASSES.stream().anyMatch(clazz::isAssignableFrom);
        return stringed ? paramValue.toString() : JsonUtil.objectToJsonStr(paramValue);
    }
}
